﻿using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml;
using DimensionsHelper.CommandLine.ConsoleTracer;
using DimensionsHelper.Common.Metadata.Axis;

namespace DimensionsHelper.CommandLine.PreTableRender;

/// <summary>
///     保存HTML中单个表格的信息。
/// </summary>
public record TableInfo(string OptionName, AxisExpression Axis);


/// <summary>
///     用于读取HTML格式的表格信息
/// </summary>
public partial class HtmlTableReader
{
    // <A NAME="Top"/>
    private const string HtmlTableTitleNodeName = "Top";

    private readonly List<TableInfo> _tables = [];
    private readonly XmlDocument _xmlDom;

    private HtmlTableReader(string htmlFilePath)
    {
        _xmlDom = new XmlDocument();
        _xmlDom.Load(htmlFilePath);
    }


    private static TableSideInfo ReadSideInfo(string xmlClassAttrValue)
    {
        string[] types = xmlClassAttrValue.Split(' ');

        TableSideTypes type = TableSideTypes.None;
        int level = 0;

        foreach (string t in types)
        {
            if (Enum.TryParse(t, true, out TableSideTypes res))
            {
                type = res;
            }
            else
            {
                Match match = AxisSubElementLevelRegex().Match(t);
                if (match.Success)
                {
                    level = int.Parse(match.Groups["lv"].Value);
                }
            }
        }

        return new TableSideInfo(type, level);
    }


    private static bool TryReadCurlyContentInTail(string text,
        [MaybeNullWhen(false)] out Tuple<string, string> result)
    {
        result = null;
        int leftCurly = text.LastIndexOf("${", StringComparison.Ordinal);
        if (leftCurly >= 0 && text.EndsWith('}'))
        {
            string left = text[..leftCurly];
            string content = text.Substring(leftCurly + 2, text.Length - leftCurly - 3);
            result = new Tuple<string, string>(left, content);
            return true;
        }

        return false;
    }


    // <tr>
    // 	<td rowspan="7" class="Axis AxisLabelIndentSide"> </td>
    // 	<td class="Axis AxisElementSide AxisElementBase">Base</td>
    // 	<td class="Cell CellTopBottom CellItemCount CellElementExpression CellElementBase">300</td>
    // </tr>
    private static void ReadSide(XmlNode node, AxisExpression axis, Stack<Tuple<int, IAxisExclusive>> stack,
        ref int level)
    {
        foreach (XmlNode? child in node.ChildNodes.Cast<XmlNode>())
        {
            string? className = child.Attributes?["class"]?.Value;
            string text = child.InnerText;

            if (string.IsNullOrEmpty(className) ||
                string.IsNullOrEmpty(text))
            {
                continue;
            }

            TableSideInfo sideInfo = ReadSideInfo(className);

            // 只读取 Combine、Net和Category，并且，此时只关注顺序，不关注具体数据，
            // 因此，此处忽略所有 class 属性以 Cell 开头 Html 节点

            switch (sideInfo.Type)
            {
                case TableSideTypes.AxisElementCategory:
                    if (TryReadCurlyContentInTail(text, out Tuple<string, string>? catCodeLabel))
                    {
                        stack.Peek().Item2
                            .Append(AxisElementFactory.CreateCategory(catCodeLabel.Item2, catCodeLabel.Item1));
                    }

                    break;

                // 遇到 Combine 时，添加到当前分类列表中即可
                case TableSideTypes.AxisElementCombine:
                    AxisElement combine = axis.Factory.CreateCombine(null, text);
                    stack.Peek().Item2.Append(combine);
                    // 添加 子元素
                    if (TryReadCurlyContentInTail(text, out Tuple<string, string>? res))
                    {
                        combine.Description = res.Item1;
                        foreach (string code in res.Item2.Split(','))
                        {
                            combine.Append(AxisElementFactory.CreateCategory(code));
                        }
                    }

                    break;

                // 进入 Net 时，递增 Level
                case TableSideTypes.AxisElementNet:
                    AxisElement net = axis.Factory.CreateNet(null, text);
                    // 不同级别的 Net，stack 依次出栈，直到级别小于当前行级别或者是 AxisExpression
                    if (level >= sideInfo.Level)
                    {
                        while (stack.Count > 1)
                        {
                            if (stack.Peek().Item1 >= level)
                            {
                                stack.Pop();
                            }
                            else
                            {
                                break;
                            }
                        }

                        level = stack.Peek().Item1;
                    }

                    stack.Peek().Item2.Append(net);
                    level++;
                    stack.Push(new Tuple<int, IAxisExclusive>(level, net));
                    break;
            }

            // 子结点中只有一个有用，读取完结束即可
            if (sideInfo.Type != TableSideTypes.None)
            {
                break;
            }
        }
    }

    [GeneratedRegex("AxisSubElementLevel(?<lv>\\d+)Side")]
    private static partial Regex AxisSubElementLevelRegex();


    // 读取 table 节点下的 <tr> 节点
    // <table cellPadding="0" cellSpacing="0">
    private static AxisExpression ReadTableRows(XmlNode node)
    {
        var expr = new AxisExpression();
        var stack = new Stack<Tuple<int, IAxisExclusive>>();
        stack.Push(new Tuple<int, IAxisExclusive>(0, expr));
        int level = 0;

        foreach (XmlNode? child in node.ChildNodes.Cast<XmlNode>())
        {
            ReadSide(child, expr, stack, ref level);
        }

        return expr;
    }


    // <body>
    private void ReadHtmlBody(XmlNode bodyNode)
    {
        bool isTitleNodeSkipped = false;
        XmlNode? node = bodyNode.FirstChild;

        while (node != null)
        {
            // <A NAME="Top"/>
            if (node.Name == "A")
            {
                // NAME
                string? nodeName = node.Attributes?["NAME"]?.Value;
                // NAME 不能是空
                if (string.IsNullOrEmpty(nodeName))
                {
                }
                else if (!isTitleNodeSkipped && nodeName == HtmlTableTitleNodeName)
                {
                    isTitleNodeSkipped = true;
                }
                else
                {
                    // 分开配置名
                    // 跳到 <table> 节点
                    while (true)
                    {
                        node = node.NextSibling;

                        if (node == null)
                        {
                            return;
                        }

                        if (node.Name == "table")
                        {
                            break;
                        }
                    }

                    // 查找表格描述
                    XmlNode? titleNode = node.SelectSingleNode("tr/td[@class='Axis AxisLabelHorizontalSide']");
                    string? titleText = titleNode?.InnerText;

                    if (string.IsNullOrEmpty(titleText) ||
                        !TryReadCurlyContentInTail(titleText, out Tuple<string, string>? names))
                    {
                        throw new Exception($"[HtmlTableReader] 无效的表格名称格式：{nodeName}");
                    }

                    _tables.Add(new TableInfo(names.Item2, ReadTableRows(node)));
                }
            }

            node = node.NextSibling;
        }
    }


    private void Read()
    {
        XmlNode bodyNode = _xmlDom.SelectSingleNode("/html/body") ??
                           throw new Exception("[HtmlTableReader] html文档中缺少'body'节点。");
        ReadHtmlBody(bodyNode);
    }


    /// <summary>
    ///     读取 html 格式表格中的表侧顺序
    /// </summary>
    /// <param name="htmlTableFilePath">html文件路径</param>
    public static List<TableInfo> ReadHtmlTable(string htmlTableFilePath)
    {
        try
        {
            var reader = new HtmlTableReader(htmlTableFilePath);
            reader.Read();
            return reader._tables;
        }
        catch (Exception e)
        {
            Tracer.Error("[HtmlTableReader] {0}", e.Message);
            return [];
        }
    }

    private enum TableSideTypes
    {
        None,
        AxisElementNet,
        AxisElementCombine,
        AxisElementCategory
    }


    private record TableSideInfo(TableSideTypes Type, int Level);
}